<script setup>
    import Spinner from '@/components/Spinner.vue'
    import TotpLooper from '@/components/TotpLooper.vue'
    import Dots from '@/components/Dots.vue'
    import twofaccountService from '@/services/twofaccountService'
    import { useUserStore } from '@/stores/user'
    import { useNotifyStore } from '@/stores/notify'
    import { UseColorMode } from '@vueuse/components'
    import { useDisplayablePassword } from '@/composables/helpers'

    const user = useUserStore()
    const notify = useNotifyStore()
    const $2fauth = inject('2fauth')
    const { copy, copied } = useClipboard({ legacy: true })
    const route = useRoute()
    
    const emit = defineEmits(['please-close-me', 'increment-hotp', 'validation-error'])
    const props = defineProps({
        otp_type : String,
        account : String,
        service : String,
        icon : String,
        secret : String,
        digits : Number,
        algorithm : String,
        period : null,
        counter : null,
        image : String,
        qrcode : null,
        uri : String
    })

    const id = ref(null)
    const uri = ref(null)
    const otpauthParams = ref({
        otp_type : '',
        account : '',
        service : '',
        icon : '',
        secret : '',
        digits : null,
        algorithm : '',
        period : null,
        counter : null,
        image : ''
    })
    const password = ref('')
    const next_password = ref('')
    const generated_at = ref(null)
    const hasTOTP = ref(false)
    const showMainSpinner = ref(false)
    const revealPassword = ref(false)
    const opacity = ref('0')

    const dots = ref()
    const totpLooper = ref()
    const otpSpanTag = ref()
    const autoCloseTimeout = ref(null)

    watch(
        () => props.icon,
        (val) => {
            if (val != undefined) {
                otpauthParams.value.icon = val
            }
        }
    )

    /***
     * 
     */
    const show = async (accountId) => {
        revealPassword.value = false

        // 3 possible cases :
        //
        //   Case 1 : When user asks for an otp of an existing account: the ID is provided so we fetch the account data
        //     from db but without the uri. This prevent the uri (a sensitive data) to transit via http request unnecessarily.
        //     In this case this.otp_type is sent by the backend.
        //
        //   Case 2 : When user uses the Quick Uploader and preview the account: No ID but we have an URI.
        //
        //   Case 3 : When user uses the Advanced form and preview the account: We should have all OTP parameter
        //     to obtain an otp, including Secret and otp_type which are required

        otpauthParams.value.otp_type = props.otp_type
        otpauthParams.value.account = props.account
        otpauthParams.value.service = props.service
        otpauthParams.value.icon = props.icon
        otpauthParams.value.secret = props.secret
        otpauthParams.value.digits = props.digits
        otpauthParams.value.algorithm = props.algorithm
        otpauthParams.value.period = props.period
        otpauthParams.value.counter = props.counter
        setLoadingState()

        // Case 1
        if (accountId) {
            id.value = accountId
            const { data } = await twofaccountService.get(id.value)

            otpauthParams.value.service = data.service
            otpauthParams.value.account = data.account
            otpauthParams.value.icon = data.icon
            otpauthParams.value.otp_type = data.otp_type

            if( isHMacBased(data.otp_type) && data.counter ) {
                otpauthParams.value.counter = data.counter
            }
        }
        // Case 2
        else if(props.uri) {
            uri.value = props.uri
            otpauthParams.value.otp_type = props.uri.slice(0, 15 ).toLowerCase() === "otpauth://totp/" ? 'totp' : 'hotp'
        }
        // Case 3
        else if (! props.secret) {
            notify.error(new Error(trans('errors.cannot_create_otp_without_secret')))
        }
        else if (! isTimeBased(otpauthParams.value.otp_type) && ! isHMacBased(otpauthParams.value.otp_type)) {
            notify.error(new Error(trans('errors.not_a_supported_otp_type')))
        }

        try {
            await getOtp()
            focusOnOTP()

            if (user.preferences.getOtpOnRequest && parseInt(user.preferences.autoCloseTimeout) > 0) {
                startAutoCloseTimer()
            }
        }
        catch(error) {
            clearOTP()
        }
    }

    /**
     * Requests and handles a fresh OTP
     */
    async function getOtp() {
        // We replace the current on screen password with the next_password to avoid having a loader.
        // The next_password will be confirmed with a new request to be synced with the backend no matter what.
        if (next_password.value) {
            password.value = next_password.value
            next_password.value = ''
            dots.value.turnOff()
            turnDotOn(0)
        }
        else {
            setLoadingState()
        }

        await getOtpPromise().then(response => {
            let otp = response.data
            password.value = otp.password
            next_password.value = otp.next_password

            if(user.preferences.copyOtpOnDisplay) {
                copyOTP(otp.password)
            }

            if (isTimeBased(otp.otp_type)) {
                generated_at.value = otp.generated_at
                otpauthParams.value.period = otp.period
                hasTOTP.value = true

                nextTick().then(() => {
                    totpLooper.value.startLoop()
                })
            }
            else if (isHMacBased(otp.otp_type)) {
                otpauthParams.value.counter = otp.counter

                // returned counter & uri are incremented
                emit('increment-hotp', { nextHotpCounter: otp.counter, nextUri: otp.uri })
            }
        })
        .catch(error => {
            if (error.response.status === 422) {
                emit('validation-error', error.response)
            }
            //throw error
        })
        .finally(() => {
            showMainSpinner.value = false
        })
    }

    /**
     * Shows blacked dots and a loading spinner
     */
    function setLoadingState() {
        showMainSpinner.value = true
        dots.value.turnOff()
    }

    /**
     * Returns the appropriate promise to get a fresh OTP from backend
     */
    function getOtpPromise() {
        if(id.value) {
            return twofaccountService.getOtpById(id.value)
        }
        else if(uri.value) {
            return twofaccountService.getOtpByUri(uri.value)
        }
        else {
            return twofaccountService.getOtpByParams(otpauthParams.value)
        }
    }

    /**
     * Triggers the component closing
     */
    function closeMe() {
        emit("please-close-me");
        revealPassword.value = false
        clearOTP()
    }

    /**
     * Reset component's refs
     */
    function clearOTP() {
        id.value = otpauthParams.value.counter = generated_at.value = null
        otpauthParams.value.service = otpauthParams.value.account = otpauthParams.value.icon = otpauthParams.value.otp_type = otpauthParams.value.secret = ''
        password.value = '... ...'
        next_password.value = ''
        hasTOTP.value = false
        clearTimeout(autoCloseTimeout.value)

        totpLooper.value?.clearLooper();
    }

    /**
     * Put focus on the OTP html tag
     */
    function focusOnOTP() {
        nextTick().then(() => {
            otpSpanTag.value?.focus()
        })
    }

    /**
     * Copies to clipboard and notify
     * 
     * @param {string} otp The password to copy
     * @param {*} permit_closing Toggle moddle closing On-Off
     */
    function copyOTP(otp, permit_closing) {
        copy(otp.replace(/ /g, ''))

        if (copied) {
            if(user.preferences.kickUserAfter == -1 && (permit_closing || false) === true && route.name != 'importAccounts') {
                user.logout({ kicked: true})
            }
            else if(user.preferences.closeOtpOnCopy && (permit_closing || false) === true) {
                closeMe()
            }

            if(user.preferences.clearSearchOnCopy) {
                emit("please-clear-search");
            }
            if (user.preferences.viewDefaultGroupOnCopy) {
                user.preferences.activeGroup = user.preferences.defaultGroup == -1 ?
                    user.preferences.activeGroup
                    : user.preferences.defaultGroup
            }

            notify.success({ text: trans('commons.copied_to_clipboard') })
        }
    }

    /**
     * Checks OTP type is Time based (TOTP)
     * 
     * @param {string} otp_type 
     */
    function isTimeBased(otp_type) {
        return (otp_type === 'totp' || otp_type === 'steamtotp')
    }

    /**
     * Checks OTP type is HMAC based (HOTP)
     * 
     * @param {string} otp_type 
     */
    function isHMacBased(otp_type) {
        return otp_type === 'hotp'
    }
    
    /**
     * Turns dots On from the first one to the provided one
     */
    function turnDotOn(dotIndex) {
        dots.value.turnOn(dotIndex)
        opacity.value = 'is-opacity-' + dotIndex
    }

    defineExpose({
        show,
        clearOTP
    })
    
    /**
     * Starts an auto close timer
     */
    function startAutoCloseTimer() {
        let duration = parseInt(user.preferences.autoCloseTimeout) // in minutes
        
        autoCloseTimeout.value = setTimeout(function() {
            closeMe()
        }, duration * 60 * 1000);
    }

</script>

<template>
    <div>
        <figure class="image is-64x64" :class="{ 'no-icon': !otpauthParams.icon }" style="display: inline-flex">
            <img :src="$2fauth.config.subdirectory + '/storage/icons/' + otpauthParams.icon" v-if="otpauthParams.icon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
        </figure>
        <UseColorMode v-slot="{ mode }">
            <p class="is-size-4 has-ellipsis" :class="mode == 'dark' ? 'has-text-grey-light' : 'has-text-grey'">{{ otpauthParams.service }}</p>
            <p class="is-size-6 has-ellipsis" :class="mode == 'dark' ? 'has-text-grey' : 'has-text-grey-light'">{{ otpauthParams.account }}</p>
            <p>
                <span
                    v-if="!showMainSpinner"
                    id="otp"
                    role="log"
                    ref="otpSpanTag"
                    tabindex="0"
                    class="otp is-size-1 is-clickable px-3"
                    :class="mode == 'dark' ? 'has-text-white' : 'has-text-grey-dark'"
                    @click="copyOTP(password, true)"
                    @keyup.enter="copyOTP(password, true)"
                    :title="$t('commons.copy_to_clipboard')"
                >
                    {{ useDisplayablePassword(password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
                </span>
                <span v-else tabindex="0" class="otp is-size-1">
                    <Spinner :isVisible="showMainSpinner" :type="'raw'" />
                </span>
            </p>
        </UseColorMode>
        <Dots v-show="isTimeBased(otpauthParams.otp_type)" ref="dots"></Dots>
        <p v-show="isHMacBased(otpauthParams.otp_type)">
            {{ $t('twofaccounts.forms.counter.label') }}: {{ otpauthParams.counter }}
        </p>
        <p v-if="user.preferences.showNextOtp" class="mt-3 is-size-4">
            <span
                v-if="next_password"
                class="is-clickable"
                :class="opacity"
                @click="copyOTP(next_password, true)"
                @keyup.enter="copyOTP(next_password, true)"
                :title="$t('commons.copy_next_password')"
            >
                {{ useDisplayablePassword(next_password, user.preferences.showOtpAsDot && user.preferences.revealDottedOTP && revealPassword) }}
            </span>
            <!-- <Spinner v-else-if="!showMainSpinner" :isVisible="true" :type="'raw'" /> -->
            <span v-else>&nbsp;</span>
        </p>
        <p v-if="user.preferences.showOtpAsDot && user.preferences.revealDottedOTP" class="mt-3">
            <button type="button" class="button is-ghost has-text-grey-dark" @click.stop="revealPassword = !revealPassword">
                <font-awesome-icon v-if="revealPassword" :icon="['fas', 'eye']" />
                <font-awesome-icon v-else :icon="['fas', 'eye-slash']" />
            </button>
        </p>
        <TotpLooper 
            v-if="hasTOTP"
            :period="otpauthParams.period" 
            :generated_at="generated_at" 
            :autostart="false" 
            v-on:loop-ended="getOtp()"
            v-on:loop-started="turnDotOn($event)"
            v-on:stepped-up="turnDotOn($event)"
            ref="totpLooper"
        ></TotpLooper>
    </div>
</template>
